package handshake

import (
	"fmt"
	"time"

	"github.com/libp2p/go-libp2p-core/peer"
	"hundsun.com/hsl/hschain/common/crypto"
	"hundsun.com/hsl/hschain/p2p/common/constant"
	"hundsun.com/hsl/hschain/p2p/impl/dht/node/blacklist"
	"hundsun.com/hsl/hschain/p2p/impl/dht/protocol"
	protosp2p "hundsun.com/hsl/hschain/protos/p2p"
)

//Peers 节点列表
type Peers []peer.AddrInfo

func init() {
	protocol.RegisterProtocolIniter(InitProtocol)
}

//Protocol Protocol
type Protocol struct {
	*protocol.P2PEnv
}

//InitProtocol 初始化
func InitProtocol(env *protocol.P2PEnv) {
	protocolInstance := &Protocol{}
	protocolInstance.Init(env)
}

//Init 初始化
func (p *Protocol) Init(env *protocol.P2PEnv) {
	p.P2PEnv = env
	//初始化peers

	go p.findPeers()
	go p.findLANPeers()
	protocol.RegisterStreamHandler(env.P2pNode.Host, constant.HandShakeAddress, p.P2pNode.OriSyncMsgResp(p.onHandShake))
}

func (p *Protocol) onHandShake(msg *protosp2p.P2PMsg) *protosp2p.P2PMsg {
	buf, err := crypto.GetLocalCertFile()
	if err != nil {
		p.Log.Errorf("get cert file failed:%v", err)
	}
	return p.P2pNode.NewP2pMessage("", "", constant.MsgHandShake, buf, nil)
}

//查询本局域网内是否有节点
func (p *Protocol) findLANPeers() {
	peerChan, err := p.P2pNode.Discovery.FindLANPeers(p.P2pNode.Host, fmt.Sprintf("/%s-mdns/%s", p.P2PEnv.Cfg.ChainID, p.P2PEnv.Cfg.Channel))
	if err != nil {
		p.Log.Error("findLANPeers", "err", err.Error())
		return
	}

	for {
		select {
		case neighbors := <-peerChan:
			//发现局域网内的邻居节点
			err := p.P2pNode.Host.Connect(p.Ctx, neighbors)
			if err != nil {
				p.Log.Error("findLANPeers", "err", err.Error())
				continue
			}
			p.Log.Info("findLANPeers", "connect neighbors success", neighbors.ID.Pretty())
			p.P2pNode.ConnMgr.AddLANStore(&neighbors)

		case <-p.Ctx.Done():
			p.Log.Info("findLANPeers", "process", "done")
			return
		}
	}
}

func (p *Protocol) findPeers() {
	peersInfo := p.P2pNode.Discovery.FindLocalPeers(p.P2pNode.ConnMgr.FetchConnPeers())
	p.RefreshAddrInfo(peersInfo)
	for {
		select {
		case <-p.P2PEnv.Ctx.Done():
			p.Log.Info("managePeers p2p closed")
			return
		case <-time.After(time.Second * 10):
			peersInfo := p.P2pNode.Discovery.FindLocalPeers(p.P2pNode.ConnMgr.FetchConnPeers())
			p.RefreshAddrInfo(peersInfo)
		}
	}
}

//RefreshAddrInfo 对新接入的节点
func (p *Protocol) RefreshAddrInfo(peers []peer.AddrInfo) {
	//检查临时白名单中的节点是否都可以连接
	p.P2pNode.Manager.Iterator(blacklist.WhitelistTmp, func(info peer.AddrInfo) {
		go func() {
			err := p.P2pNode.Host.Connect(p.Ctx, info)
			if err != nil {
				p.P2pNode.Manager.DelWhite(info.ID)
				p.Log.Infof("connect to peer:%v failed and delete", info.ID)
			}
		}()
	})

	//保存peerInfo
	_, err := p.P2pNode.Manager.Store()
	if err != nil {
		p.Log.Errorf("store peerInfo failed, reason:%v", err)
		return
	}

	//判断节点是否在白名单中
	var peersToRefresh []peer.AddrInfo

	for _, peerItem := range peers {
		if !p.P2pNode.Manager.IsConnectedPeer(peerItem.ID) {
			peersToRefresh = append(peersToRefresh, peerItem)
		}
	}
	if peersToRefresh == nil {
		return
	}
	p.Log.Debugf("peer to refresh, size:%v", len(peers))

	//加入临时黑名单
	p.P2pNode.Manager.AddBatch(peersToRefresh)

	for _, peerInfo := range peersToRefresh {
		buf, err := crypto.GetLocalCertFile()
		if err != nil {
			p.Log.Errorf("get cert file failed:%v", err)
		}
		req := p.P2pNode.NewP2pMessage("", "", constant.MsgHandShake, buf, nil)
		result, err := p.P2pNode.OriSyncMsg(req, peerInfo.ID, constant.HandShakeAddress)
		if err != nil {
			p.Log.Errorf("send handshake msg failed, pid:%v reason:%v", peerInfo.ID, err)
			continue
		}
		if result == nil {
			continue
		}
		cert := result.Payload
		//todo 证书公私钥对与peerid公私钥对统一
		//strs := strings.Split(peerInfo.Addrs[0].String(), "/")
		if !p.P2pNode.Manager.ContainsWhite(peerInfo.ID) && !p.P2pNode.AuthVerify(cert) {
			p.Log.Warnf("auth verify failed, peerId:%s, reason:%v", result.PeerId, err)
			continue
		}
		p.P2pNode.Manager.AddWhite(peerInfo)
	}
}
